package person.rk.simfnc.interchange

import person.rk.simfnc.common.Util
import kotlin.experimental.and

class Command {
    companion object{
        sealed interface SecMsgIndication{
            val exists: Boolean
            val isProprietary: Boolean
            val isStandard: Boolean
        }
        class FirstInterindustryValueSMIndication(private val _v: Byte): SecMsgIndication{
            override val exists: Boolean
                get() = _v.and(0x0c) == 0.toByte()

            override val isProprietary: Boolean
                get() = _v.and(0x0c) == 0x04.toByte()

            override val isStandard: Boolean
                get() {
                    val r = _v.and(0x0c)
                    return r == 0x08.toByte() || r == 0x0c.toByte()
                }
        }

        class FurtherInterindustryValueSMIndication(private val _v: Byte): SecMsgIndication{
            override val exists: Boolean
                get() = _v.and(0x20) == 0.toByte()

            override val isProprietary: Boolean
                get() = _v.and(0x20) == 0x20.toByte()

            override val isStandard: Boolean
                get() = _v.and(0x20) == 0x20.toByte()
        }

        sealed interface Cla{
            val isLast: Boolean
            val secureMessageIndication: SecMsgIndication
            val logicalChannelNumber: Int
        }

        sealed interface Ins{
            val code: Byte
            val name: String
        }

        sealed interface Data{
            companion object{
                interface Cls{
                    val clsName: String
                }

                interface Tag{
                    val size: Int
                        get() {
                            return expr.size
                        }
                    val expr: List<Byte>
                    val dataCls: Cls
                    val tagNumber: Int
                }
            }

            val tag: Tag
            val content: Util.ByteSlice
        }

        data class FirstInterindustryValueCla(val v: Byte): Cla{
            override val isLast: Boolean
                get() = v.and(0x10) == 0.toByte()

            override val secureMessageIndication: SecMsgIndication
                get() = FirstInterindustryValueSMIndication(v)

            override val logicalChannelNumber: Int
                get() = v.and(0x03).toInt()
        }

        data class FurtherInterindustryValueCla(val v: Byte): Cla{
            override val isLast: Boolean
                get() = v.and(0x10) == 0.toByte()

            override val secureMessageIndication: SecMsgIndication
                get() = FurtherInterindustryValueSMIndication(v)

            override val logicalChannelNumber: Int
                get() = v.and(0x0f).toInt() + 4
        }

        open class BaseIns(override val code: Byte, override val name: String): Ins

        object Ins04: BaseIns(0x04.toByte(), "DEACTIVATE FILE")
        object Ins0C: BaseIns(0x0C.toByte(), "ERASE RECORD (S)")
        object Ins0E: BaseIns(0x0E.toByte(), "ERASE BINARY")
        object Ins0F: BaseIns(0x0F.toByte(), "ERASE BINARY")
        object Ins10: BaseIns(0x10.toByte(), "PERFORM SCQL OPERATION")
        object Ins12: BaseIns(0x12.toByte(), "PERFORM TRANSACTION OPERATION")
        object Ins14: BaseIns(0x14.toByte(), "PERFORM USER OPERATION")
        object Ins20: BaseIns(0x20.toByte(), "VERIFY")
        object Ins21: BaseIns(0x21.toByte(), "VERIFY")
        object Ins22: BaseIns(0x22.toByte(), "MANAGE SECURITY ENVIRONMENT")
        object Ins24: BaseIns(0x24.toByte(), "CHANGE REFERENCE DATA")
        object Ins26: BaseIns(0x26.toByte(), "DISABLE VERIFICATION REQUIREMENT")
        object Ins28: BaseIns(0x28.toByte(), "ENABLE VERIFICATION REQUIREMENT")
        object Ins2A: BaseIns(0x2A.toByte(), "PERFORM SECURITY OPERATION")
        object Ins2C: BaseIns(0x2C.toByte(), "RESET RETRY COUNTER")
        object Ins44: BaseIns(0x44.toByte(), "ACTIVATE FILE")
        object Ins46: BaseIns(0x46.toByte(), "GENERATE ASYMMETRIC KEY PAIR")
        object Ins70: BaseIns(0x70.toByte(), "MANAGE CHANNEL")
        object Ins82: BaseIns(0x82.toByte(), "EXTERNAL (/ MUTUAL) AUTHENTICATE")
        object Ins84: BaseIns(0x84.toByte(), "GET CHALLENGE")
        object Ins86: BaseIns(0x86.toByte(), "GENERAL AUTHENTICATE")
        object Ins87: BaseIns(0x87.toByte(), "GENERAL AUTHENTICATE")
        object Ins88: BaseIns(0x88.toByte(), "INTERNAL AUTHENTICATE")
        object InsA0: BaseIns(0xA0.toByte(), "SEARCH BINARY")
        object InsA1: BaseIns(0xA1.toByte(), "SEARCH BINARY")
        object InsA2: BaseIns(0xA2.toByte(), "SEARCH RECORD")
        object InsA4: BaseIns(0xA4.toByte(), "SELECT")
        object InsB0: BaseIns(0xB0.toByte(), "READ BINARY")
        object InsB1: BaseIns(0xB1.toByte(), "READ BINARY")
        object InsB2: BaseIns(0xB2.toByte(), "READ RECORD (S)")
        object InsB3: BaseIns(0xB3.toByte(), "READ RECORD (S)")
        object InsC0: BaseIns(0xC0.toByte(), "GET RESPONSE")
        object InsC2: BaseIns(0xC2.toByte(), "ENVELOPE")
        object InsC3: BaseIns(0xC3.toByte(), "ENVELOPE")
        object InsCA: BaseIns(0xCA.toByte(), "GET DATA")
        object InsCB: BaseIns(0xCB.toByte(), "GET DATA")
        object InsD0: BaseIns(0xD0.toByte(), "WRITE BINARY")
        object InsD1: BaseIns(0xD1.toByte(), "WRITE BINARY")
        object InsD2: BaseIns(0xD2.toByte(), "WRITE RECORD")
        object InsD6: BaseIns(0xD6.toByte(), "UPDATE BINARY")
        object InsD7: BaseIns(0xD7.toByte(), "UPDATE BINARY")
        object InsDA: BaseIns(0xDA.toByte(), "PUT DATA")
        object InsDB: BaseIns(0xDB.toByte(), "PUT DATA")
        object InsDC: BaseIns(0xDC.toByte(), "UPDATE RECORD")
        object InsDD: BaseIns(0xDD.toByte(), "UPDATE RECORD")
        object InsE0: BaseIns(0xE0.toByte(), "CREATE FILE")
        object InsE2: BaseIns(0xE2.toByte(), "APPEND RECORD")
        object InsE4: BaseIns(0xE4.toByte(), "DELETE FILE")
        object InsE6: BaseIns(0xE6.toByte(), "TERMINATE DF")
        object InsE8: BaseIns(0xE8.toByte(), "TERMINATE EF")
        object InsFE: BaseIns(0xFE.toByte(), "TERMINATE CARD USAGE")

        data class SimpleTLVData(val input: Util.ByteSlice): Data{
            companion object{
                data object GenericCls: Data.Companion.Cls{
                    override val clsName: String = "Simple-TLV DATA"
                }
            }

            override val tag: Data.Companion.Tag
                get() = object: Data.Companion.Tag{
                    override val expr: List<Byte>
                        get() = listOf(input.head)
                    override val dataCls: Data.Companion.Cls
                        get() = GenericCls
                    override val tagNumber: Int
                        get() = input.head.toInt()
                }
            override val content: Util.ByteSlice
                get() {
                    val b1 = input[1]
                    val (length, offset) = if(b1 != 0xff.toByte()){
                        Pair(b1.toInt(), 1)
                    }else{
                        Pair(input.readShort(2).toInt(), 3)
                    }
                    val t1 = 1 + offset
                    return input.slice(t1, t1 + length)
                }

        }

        data class BerTLVData(val input: Util.ByteSlice): Data{
            companion object{
                enum class Cls: Data.Companion.Cls{
                    Universal{
                        override val clsName: String = "Universal"
                    },
                    Application{
                        override val clsName: String = "Application"
                    },
                    ContextSpecific{
                        override val clsName: String = "Context-specific"
                    },
                    Private{
                        override val clsName: String = "Private"
                    },
                    Unknown{
                        override val clsName: String = "Unknown"
                    }
                }

                enum class EncodingIndication{
                    Primitive, Constructed
                }
            }

            override val tag: Data.Companion.Tag
                get() = object: Data.Companion.Tag{
                    val encodingIndication: EncodingIndication
                        get() = when(input.head.and(0x20.toByte())){
                            0.toByte() -> EncodingIndication.Primitive
                            else -> EncodingIndication.Constructed
                        }
                    override val dataCls: Data.Companion.Cls
                        get() = when(input.head.and(0xc0.toByte())){
                            0x00.toByte() -> Cls.Universal
                            0x40.toByte() -> Cls.Application
                            0x80.toByte() -> Cls.ContextSpecific
                            0xc0.toByte() -> Cls.Private
                            else -> Cls.Unknown
                        }
                    override val expr: List<Byte> = when(val x = input.head.and(0x1f.toByte())){
                        0xff.toByte() -> {
                            var i = 1
                            while(input[i].and(0x80.toByte())!=0.toByte()){
                                i ++
                                if(i > 3)throw AssertionError()
                            }
                            input.slice(0, i+1).content
                        }
                        in 0 .. 30 -> listOf(input.head)
                        else -> throw AssertionError()
                    }
                    override val tagNumber: Int = when(val x = input.head.and(0x1f.toByte())){
                            0xff.toByte() -> {
                                var r = 0
                                var i = 1
                                while(input[i].and(0x80.toByte())!=0.toByte()){
                                    r = r.or(input[i].and(0x7f.toByte()).toInt()).shl(7)
                                    i ++
                                    if(i > 3)throw AssertionError()
                                }
                                r = r.or(input[i].and(0x7f.toByte()).toInt())
                                r
                            }
                            in 0 .. 30 -> x.toInt()
                            else -> throw AssertionError()
                        }
                }
            override val content: Util.ByteSlice
                get() = TODO("Not yet implemented")

        }

        fun mkIns(b: Byte): Ins {
            return when(b){
                0x04.toByte() -> Ins04
                0x0C.toByte() -> Ins0C
                0x0E.toByte() -> Ins0E
                0x0F.toByte() -> Ins0F
                0x10.toByte() -> Ins10
                0x12.toByte() -> Ins12
                0x14.toByte() -> Ins14
                0x20.toByte() -> Ins20
                0x21.toByte() -> Ins21
                0x22.toByte() -> Ins22
                0x24.toByte() -> Ins24
                0x26.toByte() -> Ins26
                0x28.toByte() -> Ins28
                0x2A.toByte() -> Ins2A
                0x2C.toByte() -> Ins2C
                0x44.toByte() -> Ins44
                0x46.toByte() -> Ins46
                0x70.toByte() -> Ins70
                0x82.toByte() -> Ins82
                0x84.toByte() -> Ins84
                0x86.toByte() -> Ins86
                0x87.toByte() -> Ins87
                0x88.toByte() -> Ins88
                0xA0.toByte() -> InsA0
                0xA1.toByte() -> InsA1
                0xA2.toByte() -> InsA2
                0xA4.toByte() -> InsA4
                0xB0.toByte() -> InsB0
                0xB1.toByte() -> InsB1
                0xB2.toByte() -> InsB2
                0xB3.toByte() -> InsB3
                0xC0.toByte() -> InsC0
                0xC2.toByte() -> InsC2
                0xC3.toByte() -> InsC3
                0xCA.toByte() -> InsCA
                0xCB.toByte() -> InsCB
                0xD0.toByte() -> InsD0
                0xD1.toByte() -> InsD1
                0xD2.toByte() -> InsD2
                0xD6.toByte() -> InsD6
                0xD7.toByte() -> InsD7
                0xDA.toByte() -> InsDA
                0xDB.toByte() -> InsDB
                0xDC.toByte() -> InsDC
                0xDD.toByte() -> InsDD
                0xE0.toByte() -> InsE0
                0xE2.toByte() -> InsE2
                0xE4.toByte() -> InsE4
                0xE6.toByte() -> InsE6
                0xE8.toByte() -> InsE8
                0xFE.toByte() -> InsFE
                else -> throw AssertionError()
            }
        }

        fun parse(bs: ByteArray): Command {
            val cmd = Command()
            val b1 = bs[0]
            cmd._cla = if (b1.and(0xe0.toByte()) == 0.toByte())
                FirstInterindustryValueCla(b1)
            else if (b1.and(0xc0.toByte()) == 0x04.toByte())
                FurtherInterindustryValueCla(b1)
            else
                throw AssertionError()
            val b2 = bs[1]
            cmd._p1 = bs[2]
            cmd._p2 = bs[3]

            val bc2 = b2.and(0xf0.toByte())
            if (bc2 == 0x60.toByte() || bc2 == 0x90.toByte()) throw AssertionError("Illegal data")
            cmd._ins = mkIns(b2)
            return cmd
        }
    }

    private var _cla: Cla? = null
    private var _ins: Ins? = null
    private var _p1: Byte = 0.toByte()
    private var _p2: Byte = 0.toByte()
    private var _data: Util.ByteSlice? = null

    private var _err: Any? = null

    val cla: Cla get() = _cla ?: throw AssertionError()

    val ins: Ins get() = _ins ?: throw AssertionError()

    val p1: Byte = _p1

    val p2: Byte = _p2
}